
Additional permission under GNU GPL version 3 section 7 */

package com.sinpo.xnfc.tech;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;

import android.nfc.tech.IsoDep;

import com.sinpo.xnfc.Util;

public class Iso7816 {
        public static final byte[] EMPTY = { 0 };

        protected byte[] data;

        protected Iso7816() {
                data = Iso7816.EMPTY;
        }

        protected Iso7816(byte[] bytes) {
                data = (bytes == null) ? Iso7816.EMPTY : bytes;
        }

        public boolean match(byte[] bytes) {
                return match(bytes, 0);
        }

        public boolean match(byte[] bytes, int start) {
                final byte[] data = this.data;
                if (data.length <= bytes.length - start) {
                        for (final byte v : data) {
                                if (v != bytes[start++])
                                        return false;
                        }
                }
                return true;
        }

        public boolean match(byte tag) {
                return (data.length == 1 && data[0] == tag);
        }

        public boolean match(short tag) {
                final byte[] data = this.data;
                if (data.length == 2) {
                        final byte d0 = (byte) (0x000000FF & tag);
                        final byte d1 = (byte) (0x000000FF & (tag >> 8));
                        return (data[0] == d0 && data[1] == d1);
                }
                return false;
        }

        public int size() {
                return data.length;
        }

        public byte[] getBytes() {
                return data;
        }

        @Override
        public String toString() {
                return Util.toHexString(data, 0, data.length);
        }

        public final static class ID extends Iso7816 {
                public ID(byte[] bytes) {
                        super(bytes);
                }
        }

        public final static class Response extends Iso7816 {
                public static final byte[] EMPTY = {};
                public static final byte[] ERROR = { 0x6F, 0x00 }; // SW_UNKNOWN

                public Response(byte[] bytes) {
                        super((bytes == null || bytes.length < 2) ? Response.ERROR : bytes);
                }

                public byte getSw1() {
                        return data[data.length - 2];
                }

                public byte getSw2() {
                        return data[data.length - 1];
                }

                public short getSw12() {
                        final byte[] d = this.data;
                        int n = d.length;
                        return (short) ((d[n - 2] << 8) | (0xFF & d[n - 1]));
                }

                public boolean isOkey() {
                        return equalsSw12(SW_NO_ERROR);
                }

                public boolean equalsSw12(short val) {
                        return getSw12() == val;
                }

                public int size() {
                        return data.length - 2;
                }

                public byte[] getBytes() {
                        return isOkey() ? Arrays.copyOfRange(data, 0, size())
                                        : Response.EMPTY;
                }
        }

        public final static class BerT extends Iso7816 {
                // tag template
                public static final byte TMPL_FCP = 0x62; // File Control Parameters
                public static final byte TMPL_FMD = 0x64; // File Management Data
                public static final byte TMPL_FCI = 0x6F; // FCP and FMD

                // proprietary information
                public final static BerT CLASS_PRI = new BerT((byte) 0xA5);
                // short EF identifier
                public final static BerT CLASS_SFI = new BerT((byte) 0x88);
                // dedicated file name
                public final static BerT CLASS_DFN = new BerT((byte) 0x84);
                // application data object
                public final static BerT CLASS_ADO = new BerT((byte) 0x61);
                // application id
                public final static BerT CLASS_AID = new BerT((byte) 0x4F);

                public static int test(byte[] bytes, int start) {
                        int len = 1;
                        if ((bytes[start] & 0x1F) == 0x1F) {
                                while ((bytes[start + len] & 0x80) == 0x80)
                                        ++len;

                                ++len;
                        }
                        return len;
                }

                public static BerT read(byte[] bytes, int start) {
                        return new BerT(Arrays.copyOfRange(bytes, start,
                                        start + test(bytes, start)));
                }

                public BerT(byte tag) {
                        this(new byte[] { tag });
                }

                public BerT(short tag) {
                        this(new byte[] { (byte) (0x000000FF & (tag >> 8)),
                                        (byte) (0x000000FF & tag) });
                }

                public BerT(byte[] bytes) {
                        super(bytes);
                }

                public boolean hasChild() {
                        return ((data[0] & 0x20) == 0x20);
                }
        }

        public final static class BerL extends Iso7816 {
                private final int val;

                public static int test(byte[] bytes, int start) {
                        int len = 1;
                        if ((bytes[start] & 0x80) == 0x80) {
                                len += bytes[start] & 0x07;
                        }
                        return len;
                }

                public static int calc(byte[] bytes, int start) {
                        if ((bytes[start] & 0x80) == 0x80) {
                                int v = 0;

                                int e = start + bytes[start] & 0x07;
                                while (++start <= e) {
                                        v <<= 8;
                                        v |= bytes[start] & 0xFF;
                                }

                                return v;
                        }

                        return bytes[start];
                }

                public static BerL read(byte[] bytes, int start) {
                        return new BerL(Arrays.copyOfRange(bytes, start,
                                        start + test(bytes, start)));
                }

                public BerL(byte[] bytes) {
                        super(bytes);
                        val = calc(bytes, 0);
                }

                public int toInt() {
                        return val;
                }
        }

        public final static class BerV extends Iso7816 {
                public static BerV read(byte[] bytes, int start, int len) {
                        return new BerV(Arrays.copyOfRange(bytes, start, start + len));
                }

                public BerV(byte[] bytes) {
                        super(bytes);
                }
        }

        public final static class BerTLV extends Iso7816 {
                public static int test(byte[] bytes, int start) {
                        final int lt = BerT.test(bytes, start);
                        final int ll = BerL.test(bytes, start + lt);
                        final int lv = BerL.calc(bytes, start + lt);

                        return lt + ll + lv;
                }

                public static BerTLV read(Iso7816 obj) {
                        return read(obj.getBytes(), 0);
                }

                public static BerTLV read(byte[] bytes, int start) {
                        int s = start;
                        final BerT t = BerT.read(bytes, s);
                        s += t.size();

                        final BerL l = BerL.read(bytes, s);
                        s += l.size();

                        final BerV v = BerV.read(bytes, s, l.toInt());
                        s += v.size();

                        final BerTLV tlv = new BerTLV(t, l, v);
                        tlv.data = Arrays.copyOfRange(bytes, start, s);

                        return tlv;
                }

                public static ArrayList<BerTLV> readList(Iso7816 obj) {
                        return readList(obj.getBytes());
                }

                public static ArrayList<BerTLV> readList(final byte[] data) {
                        final ArrayList<BerTLV> ret = new ArrayList<BerTLV>();

                        int start = 0;
                        int end = data.length - 3;
                        while (start < end) {
                                final BerTLV tlv = read(data, start);
                                ret.add(tlv);

                                start += tlv.size();
                        }

                        return ret;
                }

                public final BerT t;
                public final BerL l;
                public final BerV v;

                public BerTLV(BerT t, BerL l, BerV v) {
                        this.t = t;
                        this.l = l;
                        this.v = v;
                }

                public BerTLV getChildByTag(BerT tag) {
                        if (t.hasChild()) {
                                final byte[] raw = v.getBytes();
                                int start = 0;
                                int end = raw.length;
                                while (start < end) {
                                        if (tag.match(raw, start))
                                                return read(raw, start);

                                        start += test(raw, start);
                                }
                        }

                        return null;
                }

                public BerTLV getChild(int index) {
                        if (t.hasChild()) {
                                final byte[] raw = v.getBytes();
                                int start = 0;
                                int end = raw.length;

                                int i = 0;
                                while (start < end) {
                                        if (i++ == index)
                                                return read(raw, start);

                                        start += test(raw, start);
                                }
                        }

                        return null;
                }
        }

        public final static class Tag {
                private final IsoDep nfcTag;
                private ID id;

                public Tag(IsoDep tag) {
                        nfcTag = tag;
                        id = new ID(tag.getTag().getId());
                }

                public ID getID() {
                        return id;
                }

                public Response verify() {
                        final byte[] cmd = { (byte) 0x00, // CLA Class
                                        (byte) 0x20, // INS Instruction
                                        (byte) 0x00, // P1 Parameter 1
                                        (byte) 0x00, // P2 Parameter 2
                                        (byte) 0x02, // Lc
                                        (byte) 0x12, (byte) 0x34, };

                        return new Response(transceive(cmd));
                }

                public Response initPurchase(boolean isEP) {
                        final byte[] cmd = {
                                        (byte) 0x80, // CLA Class
                                        (byte) 0x50, // INS Instruction
                                        (byte) 0x01, // P1 Parameter 1
                                        (byte) (isEP ? 2 : 1), // P2 Parameter 2
                                        (byte) 0x0B, // Lc
                                        (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                                        (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x33,
                                        (byte) 0x44, (byte) 0x55, (byte) 0x66, (byte) 0x0F, // Le
                        };

                        return new Response(transceive(cmd));
                }

                public Response getBalance(boolean isEP) {
                        final byte[] cmd = { (byte) 0x80, // CLA Class
                                        (byte) 0x5C, // INS Instruction
                                        (byte) 0x00, // P1 Parameter 1
                                        (byte) (isEP ? 2 : 1), // P2 Parameter 2
                                        (byte) 0x04, // Le
                        };

                        return new Response(transceive(cmd));
                }

                public Response readRecord(int sfi, int index) {
                        final byte[] cmd = { (byte) 0x00, // CLA Class
                                        (byte) 0xB2, // INS Instruction
                                        (byte) index, // P1 Parameter 1
                                        (byte) ((sfi << 3) | 0x04), // P2 Parameter 2
                                        (byte) 0x00, // Le
                        };

                        return new Response(transceive(cmd));
                }

                public Response readRecord(int sfi) {
                        final byte[] cmd = { (byte) 0x00, // CLA Class
                                        (byte) 0xB2, // INS Instruction
                                        (byte) 0x01, // P1 Parameter 1
                                        (byte) ((sfi << 3) | 0x05), // P2 Parameter 2
                                        (byte) 0x00, // Le
                        };

                        return new Response(transceive(cmd));
                }

                public Response readBinary(int sfi) {
                        final byte[] cmd = { (byte) 0x00, // CLA Class
                                        (byte) 0xB0, // INS Instruction
                                        (byte) (0x00000080 | (sfi & 0x1F)), // P1 Parameter 1
                                        (byte) 0x00, // P2 Parameter 2
                                        (byte) 0x00, // Le
                        };

                        return new Response(transceive(cmd));
                }

                public Response readData(int sfi) {
                        final byte[] cmd = { (byte) 0x80, // CLA Class
                                        (byte) 0xCA, // INS Instruction
                                        (byte) 0x00, // P1 Parameter 1
                                        (byte) (sfi & 0x1F), // P2 Parameter 2
                                        (byte) 0x00, // Le
                        };

                        return new Response(transceive(cmd));
                }

                public Response selectByID(byte... name) {
                        ByteBuffer buff = ByteBuffer.allocate(name.length + 6);
                        buff.put((byte) 0x00) // CLA Class
                                        .put((byte) 0xA4) // INS Instruction
                                        .put((byte) 0x00) // P1 Parameter 1
                                        .put((byte) 0x00) // P2 Parameter 2
                                        .put((byte) name.length) // Lc
                                        .put(name).put((byte) 0x00); // Le

                        return new Response(transceive(buff.array()));
                }

                public Response selectByName(byte... name) {
                        ByteBuffer buff = ByteBuffer.allocate(name.length + 6);
                        buff.put((byte) 0x00) // CLA Class
                                        .put((byte) 0xA4) // INS Instruction
                                        .put((byte) 0x04) // P1 Parameter 1
                                        .put((byte) 0x00) // P2 Parameter 2
                                        .put((byte) name.length) // Lc
                                        .put(name).put((byte) 0x00); // Le

                        return new Response(transceive(buff.array()));
                }

                public void connect() {
                        try {
                                nfcTag.connect();
                        } catch (Exception e) {
                        }
                }

                public void close() {
                        try {
                                nfcTag.close();
                        } catch (Exception e) {
                        }
                }

                public byte[] transceive(final byte[] cmd) {
                        try {
                                return nfcTag.transceive(cmd);
                        } catch (Exception e) {
                                return Response.ERROR;
                        }
                }
        }

        public static final short SW_NO_ERROR = (short) 0x9000;
        public static final short SW_BYTES_REMAINING_00 = 0x6100;
        public static final short SW_WRONG_LENGTH = 0x6700;
        public static final short SW_SECURITY_STATUS_NOT_SATISFIED = 0x6982;
        public static final short SW_FILE_INVALID = 0x6983;
        public static final short SW_DATA_INVALID = 0x6984;
        public static final short SW_CONDITIONS_NOT_SATISFIED = 0x6985;
        public static final short SW_COMMAND_NOT_ALLOWED = 0x6986;
        public static final short SW_APPLET_SELECT_FAILED = 0x6999;
        public static final short SW_WRONG_DATA = 0x6A80;
        public static final short SW_FUNC_NOT_SUPPORTED = 0x6A81;
        public static final short SW_FILE_NOT_FOUND = 0x6A82;
        public static final short SW_RECORD_NOT_FOUND = 0x6A83;
        public static final short SW_INCORRECT_P1P2 = 0x6A86;
        public static final short SW_WRONG_P1P2 = 0x6B00;
        public static final short SW_CORRECT_LENGTH_00 = 0x6C00;
        public static final short SW_INS_NOT_SUPPORTED = 0x6D00;
        public static final short SW_CLA_NOT_SUPPORTED = 0x6E00;
        public static final short SW_UNKNOWN = 0x6F00;
        public static final short SW_FILE_FULL = 0x6A84;
}
